package codename.carrot.bomberman.models;

import java.util.ArrayList;

import sheep.game.Sprite;
import sheep.graphics.Image;
import sheep.graphics.SpriteView;
import codename.carrot.bomberman.AppStorage;
import codename.carrot.bomberman.AppStorage.Direction;
import codename.carrot.bomberman.AppStorage.Power;
import codename.carrot.bomberman.BitmapFactory;
import codename.carrot.bomberman.R;
import codename.carrot.bomberman.game.listener.PlayerLifeListener;
import codename.carrot.bomberman.game.listener.PlayerPositionListener;
import codename.carrot.bomberman.game.listener.PlayerPowerUpListener;
import codename.carrot.bomberman.game.networkobjects.DroidPoint;

/**
 *  A class representing a Player
 * @author Group A4
 *
 */


public class Player extends Sprite {
	/**
	 * List of listeners for changes to the power-ups of this player
	 */
	private ArrayList<PlayerPowerUpListener> powerupListeners = new ArrayList<PlayerPowerUpListener>();
	/**
	 *  List of listeners for changes to the position, and movement, of this player
	 */
	private ArrayList<PlayerPositionListener> positionListeners = new ArrayList<PlayerPositionListener>();
	/**
	 *  List of listeners for changes to the life of this player
	 */
	private ArrayList<PlayerLifeListener> lifeListeners = new ArrayList<PlayerLifeListener>();

	/**
	 *  Enum with the available colors for players	 
	 */
	public enum PlayerColor {
	    /**
	     *  Specifies a purple player color
	     */
		PURPEL, 
		/**
		 *  Specifies a yellow player color
		 */
		YELLOW, 
		/**
		 *  Specifies a blue player color
		 */
		BLUE, 
		/**
		 *  Specifies the orange color
		 */
		ORANGE;
	};
	
	/**
	 * Initializing playerId to be -1 ( PlayerIDs start at 0, and are given by the server)
	 */
	private int playerId = -1;
	/**
	 * tells how many extra bombs the player can have on the map simultanously (0-5)
	 */
	private int bombUp = 0;
	/**
	 *  tells how fast the player can walk (from -5 to 5)
	 */
	private int speedUp = 0;
	/**
	 * tells how many tiles each flame/explosion spans (0-5)
	 */
	private int flameUp = 0;
	
	/**
	 * tells the time of death for this player, in unix timestamp
	 */
	private long timeOfDeath;

	/**
	 * Direction of movement
	 */
	public Direction direction;
	/**
	 * the current x-coordinate of the player
	 */
	private int xCoorMap,
	/**
	 * the current y-coordinate of the player
	 */
	yCoorMap;
	
	/**
	 * the nickname of the player
	 */
	private String nickname;
	/**
	 * the images used for the player animation (one for each direction)
	 */
	private Image[] currentView, 
	left, 
	right, 
	up, 
	down;
	private int currentFrame = 0;
	private float tickTime = 0.1f;
	private float timeLeft;
	/**
	 * the number of lives the player has
	 */
	private int life;
	private boolean isDead = false;
	/**
	 * For movement of this Player
	 */
	private boolean isMoving = false;
	/**
	 * tells whether or not this player should move (even if movement has not yet commenced)
	 */
	private boolean shouldMove = false;
	/**
	 * the size of each tile
	 */
	private float gridSize = AppStorage.TILE_SIZE;
	/**
	 * a timer used for movement from tile-to-tile
	 */
	private float moveTimer = AppStorage.TILE_SIZE;
	/**
	 * the speed of the player movement
	 */
	private float velocity = gridSize * 3;
	/**
	 * the speed of movement in x-direction
	 */
	private float speedX = 0;
	/**
	 * the speed of movement in y-direction
	 */
	private float speedY = 0;
	/**
	 * the number of miliseconds the player is immune to damage ( after collision with a flame.. )
	 */
	public long damageImmunity;
 
	/**
	 *  An enum with all available power-up types	 
	 */
	private enum powerUpType {
		SPEED, 
		BOMB, 
		FLAME, 
		BOMB_THROW, 
		BOMB_KICK
	}

	/**
	 *  Constructor
	 * @param xCoorMap the initial x-coordinate of the player
	 * @param yCoorMap the initial y-coordinate of the player
	 * @param playerId the ID of the player
	 * @param nickname the nickname of the player
	 */
	public Player(int xCoorMap, int yCoorMap, int playerId, String nickname) {
		super(new Image(R.drawable.blue_1_d));
		// Applying default color (as blue)
		setColor(PlayerColor.BLUE);
		// ---- SET UP PLAYER ANIMATION IMAGES ----
		this.playerId = playerId;
		this.nickname = nickname;
		this.setDirection();
		this.xCoorMap = xCoorMap;
		this.yCoorMap = yCoorMap;
		// Every player starts with 5 lives
		life = 5;
		// damageImmunity specifies a number of miliseconds, during which the player is
		// immune to damage by flames/bombs
		damageImmunity = 0;
		setPosition(xCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE / 2,
				yCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE / 2);
	}
	
	/**
	 *  Sets the color for this player
	 * @param playerColor the color to apply
	 */
	public void setColor(PlayerColor playerColor) {	    
	    // SETTING UP ANIMATION IMAGES FOR PLAYERCOLOR
        this.left = BitmapFactory.buildPlayerView(playerColor, Direction.LEFT);
        this.right = BitmapFactory.buildPlayerView(playerColor, Direction.RIGHT);
        this.up = BitmapFactory.buildPlayerView(playerColor, Direction.UP);
        this.down = BitmapFactory.buildPlayerView(playerColor, Direction.DOWN);
        this.currentView = left;
        this.setView((SpriteView) currentView[currentFrame]);	   
	}
	
	/**
	 *  Returns the ID of this player
	 * @return The player id.
	 */
	public int getPlayerId() {
		return playerId;
	}

	/**
	 * @Deprecated use setInitialPlayerPosition instead
	 *  Sets the initial position of this player
	 * @param xCoorMap
	 * @param yCoorMap
	 */
	public void setInitialPosition(int xCoorMap, int yCoorMap) {
		setPosition(xCoorMap * AppStorage.PLAYER_SIZE + AppStorage.PLAYER_SIZE / 2,
				yCoorMap * AppStorage.PLAYER_SIZE + AppStorage.PLAYER_SIZE / 2);
	}

	/**
	 * Returns the direction of movement for this player
	 * @return The direction.
	 */
	public Direction getDirection() {
		return direction;
	}

	/**
	 *  Updates and sets the direction of movement for this player
	 */
	private void setDirection() {
		if (direction == null)
			direction = Direction.RIGHT;
		switch (direction) {
		case UP:
			currentView = up;
			break;
		case DOWN:
			currentView = down;
			break;
		case LEFT:
			currentView = left;
			break;
		case RIGHT:
			currentView = right;
			break;
		default:
			currentView = right;
		}
	}

	/**
	 *  Returns the nickname of this player
	 * @return nickname
	 */
	public String getNickname() {
		if (nickname == null)
			return "NN";
		return nickname;
	}

	@Override
	public boolean collides(Sprite otherSprite) {
		if (otherSprite == null)
			return false;
		return super.collides(otherSprite);
	}

	/**
	 *  Sets that this player is dead if <code> dead</code> is true, false if not
	 * @param dead true|false
	 */
	public void setIsDead(boolean dead) {
	    if(dead) timeOfDeath = System.currentTimeMillis();
		this.isDead = dead;		
		//Alert server of death.
	}

	/**
	 *  Returns the time of death for this player
	 * @return time of death
	 */
	public long getTimeOfDeath() {
	    return timeOfDeath;
	}
	
	/**
	 * Returns true if this player is dead, false if not
	 * @return true|false
	 */
	public boolean isDead() {
		return isDead;
	}

	/**
	 *  Returns true if this player is moving, false if not
	 * @return true|false
	 */
	public boolean isMoving() {
		return isMoving;
	}

	/**
	 * Starts movement
	 */
	private void beginMoving() {
	    if(direction == null) return;
		velocity = gridSize * 3 + speedUp * (gridSize / 2);
		if (!isMoving) {
			// fireBeginMoving();
		    fireMovementBegins();
			isMoving = true;
			moveTimer = AppStorage.TILE_SIZE;
			switch (direction) {
			case UP:
				speedX = 0;
				speedY = -velocity;
				break;
			case DOWN:
				speedX = 0;
				speedY = velocity;
				break;
			case LEFT:
				speedX = -velocity;
				speedY = 0;
				break;
			case RIGHT:
				speedX = velocity;
				speedY = 0;
				break;
			default:
				speedX = 0;
				speedY = 0;
				break;
			}			
		}
	}

	/**
	 *  Starts moving in a specified direction
	 */
	public void startMoving() {
		shouldMove = true;
		if (!isMoving) {
			beginMoving();
		}
	}

	/**
	 *  Stops moving when player is done with current movement
	 */
	public void stopMoving() {
		shouldMove = false;
	}

	/**
	 * Terminates movement (even if player is not centered in a tile)
	 */
	public void cancelMoving() {
		isMoving = false;
		shouldMove = false;
		// direction = null;
	}

	/**
	 * Updates the state of this player based on number of miliseconds since last update
	 */
	@Override
	public void update(float dt) {		
		super.update(dt);
		if (isMoving) {
			timeLeft += dt;

			// Flip to next frame
			if (timeLeft >= tickTime) {
				currentFrame = (currentFrame + 1) % currentView.length;
				setView((SpriteView) currentView[currentFrame]);
				timeLeft -= tickTime;
			}
			setDirection();
			float dx, dy, x, y;
			// heading is from 0-2pi
			x = getX();
			y = getY();
			dx = speedX * dt;
			dy = speedY * dt;
			setPosition(x + dx, y + dy);
			// if dx is not zero, then dy is.. and the other way around (works
			// fine)
			moveTimer -= Math.abs(dx);
			moveTimer -= Math.abs(dy);
			if (moveTimer <= 0) {
				setPosition(getSnapToGridXpos(), getSnapToGridYpos());
				isMoving = false;
				// direction = null;
				fireMovementEnds(getXcoorMap(), getYcoorMap());
				// fireStopMoving();
			}
		}
		if (xCoorMap != getXcoorMap()) {
			xCoorMap = getXcoorMap();
			fireXcoorMapChanged();
		}

		if (yCoorMap != getYcoorMap()) {
			yCoorMap = getYcoorMap();
			fireYcoorMapChanged();
		}
		if (shouldMove) {
			beginMoving();
		}
	}
	
	/**
	 *  Notifies listeners that thisplayer has started moving
	 */
	private void fireMovementBegins() {
        if(positionListeners != null && !positionListeners.isEmpty()) {
            for(PlayerPositionListener pp : positionListeners) {
                pp.onMovementBegins(getXcoorMap(), getYcoorMap(), this);
            }
        }
	}
	
	/**
	 *  Notifies listeners that this player has stopped moving
	 * @param xCoorMap
	 * @param yCoorMap
	 */
	private void fireMovementEnds(int xCoorMap, int yCoorMap) {
	    if(positionListeners != null && !positionListeners.isEmpty()) {
	        for(PlayerPositionListener pp : positionListeners) {
	            pp.onMovementEnds(xCoorMap, yCoorMap, this);
	        }
	    }
	}

	/**
	 *  Returns the closest real-x-coordinate to which this player should snap-to-grid-to
	 * @return The closest x-coordinate.
	 */
	private float getSnapToGridXpos() {
		int xCoorMap = getXcoorMap();
		return xCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE / 2;
	}

	/**
	 *  Returns the closest real-y-coordinate to which this player should snap-to-grid to
	 * @return The closest y-coordinate.
	 */
	private float getSnapToGridYpos() {
		int yCoorMap = getYcoorMap();
		return yCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE / 2;
	}

	/**
	 *  Returns the Y-coordinate (grid-coordinate) for this player
	 * @return yCoordinate
	 */
	public int getYcoorMap() {
		// return (getY() + DroidApp.PLAYER_SIZE/2) / DroidApp.TILE_SIZE;
		return (int) Math.ceil((getY() - AppStorage.PLAYER_SIZE / 2)
				/ AppStorage.TILE_SIZE - 0.5f);
	}

	/**
	 *  Returns the X-coordinate (grid-coordinate) for this player
	 * @return xCoordinate
	 */
	public int getXcoorMap() {
		return (int) Math.ceil((getX() - AppStorage.PLAYER_SIZE / 2)
				/ AppStorage.TILE_SIZE - 0.5f);
	}

	/**
	 *  Increases player speed, if <code>speedUp</code> is < 5 and notifies listeners of changes, if changes were made
	 */
	public void speedUp() {
		if (speedUp < 5) {
			speedUp++;
			firePlayerGotNewPowerUp(powerUpType.SPEED, Power.UP);
		}
	}

	/**
	 * Decreases player speed, if <code>speedUp</code> is > -5, 
	 * and notifies listeners of changes, if changes were made
	 */
	public void speedDown() {
		if (speedUp > -5) {
			speedUp--;
			firePlayerGotNewPowerUp(powerUpType.SPEED, Power.DOWN);
		}
	}

	/**
	 * Decreases the number of bombs this player can have active, if <code>bombUp</code> > 0, 
	 * and notifies listeners of changes, if changes were made
	 */
	public void bombDown() {
		if (bombUp > 0) {
			bombUp--;
			firePlayerGotNewPowerUp(powerUpType.BOMB, Power.DOWN);
		}
	}

	/**
	 * Increases the number of bombs this player can have active, if <code>bombUp</code> < 5,
	 * and notifies listeners of changes, if changes were made
	 */
	public void bombUp() {
		if (bombUp < 5) {
			bombUp++;
			firePlayerGotNewPowerUp(powerUpType.BOMB, Power.UP);
		}
	}

	/**
	 *  Increases the length of bomb-explosions for this player by 1, if <code>flameUp</code> is < 5, and notifies listeners
	 *  of changes, if changes were made
	 */
	public void flameUp() {
		if (flameUp < 5) {
			flameUp++;
			firePlayerGotNewPowerUp(powerUpType.FLAME, Power.UP);
		}
	}

   /**
     *  Decreases the length of bomb-explosions for this player by 1, if <code>flameUp</code> is > 0, and notifies listeners
     *  of changes, if changes were made
     */
	public void flameDown() {
		if (flameUp > 0) {
			flameUp--;
			firePlayerGotNewPowerUp(powerUpType.FLAME, Power.DOWN);
		}
	}

	/**
	 * Returns the length of bomb-explosions for this player
	 * @return flame length
	 */
	public int getFlameUps() {
		return flameUp;
	}

	/**
	 *  Returns the speed of this player
	 * @return player speed
	 */
	public int getSpedUps() {
		return speedUp;
	}

	/**
	 *  Returns the number of bombs this player can have active
	 * @return bomb-up
	 */
	public int getBombUps() {
		return bombUp;
	}

	/**
	 * Returns the nr of lives the player has
	 * @return life
	 */
	public int getLife() {
		return life;
	}
	
	/**
	 * Sets the numer of lives of this player to <code>life</code>
	 * @param life nr of lives the player has
	 */
	public void setLife(int life) {
	    this.life = life;	    
	}

	/**
	 *  Handles item-pickup, and changes the attribute(s) of the player accordingly
	 * @param c the item player picked up
	 */
	public void pickedUp(CollectableItem c) {	   
		switch (c.getType()) {
		case CollectableItem.ITEM_BOMB:
			if (!c.isPowerup()) {
				bombDown();
			} else
				bombUp();
			break;
		case CollectableItem.ITEM_FLAME:
			if (!c.isPowerup()) {
				flameDown();
			} else
				flameUp();
			break;
		case CollectableItem.ITEM_SPEED:
			if (!c.isPowerup()) {
				speedDown();
			} else
				speedUp();
			break;
		}

	}

	/**
	 *  Subtracts 1 from the amount of lives the player has, and notifies listeners
	 */
	public void playerLostLife() {
		life--;
		firePlayerLostLife();
	}

	/**
	 *  Notifies listeners that the player has lost a life
	 */
	public void firePlayerLostLife() {
		for (PlayerLifeListener pl : lifeListeners)
			pl.playerLostLife(life, this);
	}

	/**
	 *  Fires an event notifying listeners that the player has received a new power-up
	 * @param type type of powerup
	 * @param power whether it's positive/negative
	 */
	public void firePlayerGotNewPowerUp(powerUpType type, Power power) {
		switch (type) {
		case SPEED:
			for (PlayerPowerUpListener pu : powerupListeners)
				pu.speedChange(this.speedUp, power);
			break;
		case BOMB:
			for (PlayerPowerUpListener pu : powerupListeners)
				pu.bombChange(this.bombUp, power);
			break;
		case FLAME:
			for (PlayerPowerUpListener pu : powerupListeners)
				pu.flameChange(this.flameUp, power);
			break;
		}
	}

	/**
	 *  Fires an event notifying listeners that the player has moved to a different X-coordinate
	 */
	private void fireXcoorMapChanged() {
		for (PlayerPositionListener pp : positionListeners)
			pp.onXcoorGridChanged(xCoorMap, this);
	}

	/**
	 *  Fires an event notifying listeners that the player has moved to a different Y-coordinate
	 */
	private void fireYcoorMapChanged() {
		for (PlayerPositionListener pp : positionListeners)
			pp.onYCoorGridChanged(yCoorMap, this);
	}

    /**
     * Adds a player position listener
     * @param listener the listener to add
     */
	public void addPlayerPositionListener(PlayerPositionListener listener) {
		positionListeners.add(listener);
	}

    /**
     * Adds a player life listener
     * @param listener the listener to add
     */
	public void addPlayerLifeListener(PlayerLifeListener listener) {
		lifeListeners.add(listener);
	}

    /**
     * Adds a player power up listener
     * @param listener the listener to add
     */
	public void addPlayerPowerUpListener(PlayerPowerUpListener listener) {
		powerupListeners.add(listener);
	}

    /**
     * Removes a player position  listener
     * @param listener the listener to remove
     */
	public void removePlayerPositionListener(PlayerPositionListener listener) {
		positionListeners.remove(listener);
	}

    /**
     * Removes a player life listener
     * @param listener the listener to remove
     */
	public void removePlayerLifeListener(PlayerLifeListener listener) {
		lifeListeners.remove(listener);
	}
	
	/**
	 * Removes a player power up listener
	 * @param listener the listener to remove
	 */
	public void removePlayerPowerUpListener(PlayerLifeListener listener) {
		powerupListeners.remove(listener);
	}

	/**
	 *  Updates the direction of movement for this player
	 * @param direction the direction of movement
	 */
	public void updateDirection(Direction direction) {
		this.direction = direction;
	}

	/**
	 *  Resets this player in the center of a specific grid-coordinate
	 * @param xCoorMap x-coordinate on the grid
	 * @param yCoorMap y-coorinate on the grid
	 */
	public void setMapPosition(int xCoorMap, int yCoorMap) {
		setPosition(xCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE/ 2, yCoorMap * AppStorage.TILE_SIZE + AppStorage.PLAYER_SIZE/ 2);
	}	

	/**
	 *  Sets the initial position for this player
	 * @param initialPlayerPosition
	 */
    public void setInitialPlayerPosition(DroidPoint initialPlayerPosition) {
        setPosition(initialPlayerPosition.x * AppStorage.PLAYER_SIZE + AppStorage.PLAYER_SIZE / 2, initialPlayerPosition.y * AppStorage.PLAYER_SIZE + AppStorage.PLAYER_SIZE / 2);        
    }
}
